This JavaScript program demonstrates how to draw a plane cut by a cube with transparency in a WebGL program.
<!DOCTYPE html> <html> <head> <title>XoaX.net's WebGL</title> <script id="idVertexShader" type="c"> attribute vec4 av4Vertex; attribute vec4 av4Color; varying vec4 vv4Color; void main() { gl_Position = av4Vertex; gl_PointSize = 5.0; vv4Color = av4Color; } </script> <script id="idFragmantShader" type="c"> precision mediump float; varying vec4 vv4Color; void main() { gl_FragColor = vv4Color; } </script> <script type="text/javascript"> var gqGL = null; var gqProgram = null; function CreateProgramAndContext() { // Get the WebGL Context var qCanvas = document.querySelector("#idCanvas"); gqGL = qCanvas.getContext("webgl"); // Compile the vertex shader var sVertexShaderCode = document.querySelector("#idVertexShader").text; var qVertexShader = gqGL.createShader(gqGL.VERTEX_SHADER); gqGL.shaderSource(qVertexShader, sVertexShaderCode); gqGL.compileShader(qVertexShader); // Compile the fragment shader var sFragmentShaderCode = document.querySelector("#idFragmantShader").text; var qFragmentShader = gqGL.createShader(gqGL.FRAGMENT_SHADER); gqGL.shaderSource(qFragmentShader, sFragmentShaderCode); gqGL.compileShader(qFragmentShader); // Compile and link the program gqProgram = gqGL.createProgram(); gqGL.attachShader(gqProgram, qVertexShader); gqGL.attachShader(gqProgram, qFragmentShader); gqGL.linkProgram(gqProgram); gqGL.useProgram(gqProgram); } var gfaTransformedVertices = null; var gfaVertexColors = null; function CreateBuffers() { var qVerticesBuffer = gqGL.createBuffer(); gqGL.bindBuffer(gqGL.ARRAY_BUFFER, qVerticesBuffer); gqGL.bufferData(gqGL.ARRAY_BUFFER, gfaTransformedVertices, gqGL.STATIC_DRAW); var qVertexLoc = gqGL.getAttribLocation(gqProgram, 'av4Vertex'); gqGL.vertexAttribPointer(qVertexLoc, 4, gqGL.FLOAT, false, 0, 0); gqGL.enableVertexAttribArray(qVertexLoc); var qColorsBuffer = gqGL.createBuffer(); gqGL.bindBuffer(gqGL.ARRAY_BUFFER, qColorsBuffer); gqGL.bufferData(gqGL.ARRAY_BUFFER, gfaVertexColors, gqGL.STATIC_DRAW); var qColors = gqGL.getAttribLocation(gqProgram, 'av4Color'); gqGL.vertexAttribPointer(qColors, 4, gqGL.FLOAT, false, 0, 0); gqGL.enableVertexAttribArray(qColors); } var gfaVertices = null; var giAddedPoints = 0; function Initialization() { gfaVertices = new Float32Array([ // These must be drawn back to front to render it correctly with the alpha blending -1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, 1.0, // x = -1 1.0, -1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, 1.0, // y = -1 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, // z = -1 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, // x = 1 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, // y = 1 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, // z = 1 // Put the vertices for the diamond first, then the cube vertices 0.0, 0.85, 0.0, 1.0, 0.85, 0.0, 0.85, 1.0, -0.85, 0.0, -0.85, 1.0, 0.0, -0.85, 0.0, 1.0, // Add zeroes that will be written over 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ]); faaEdges = [ [-1, -1, 0], [1, -1, 0], [-1, 1, 0], [1, 1, 0], [-1, 0, -1], [1, 0, -1], [-1, 0, 1], [1, 0, 1], [0, -1, -1], [0, 1, -1], [0, -1, 1], [0, 1, 1] ]; // Ax + By + CZ + D = 0 var faPlane = [2.0, 5.0, 3.0, 2.0]; // Get the planar-intersection polygon var faPolygon = GetPlaneBoxIntersectionPolygon(faPlane, [[-1.0, 1.0],[-1.0, 1.0],[-1.0, 1.0]]); // Copy the polygon vertices. for (var i = 0; i < faPolygon.length; ++i) { gfaVertices[28*4 + i] = faPolygon[i]; } // Each point has 4 coordinates giAddedPoints = faPolygon.length/4; var iVertices = (28 + giAddedPoints); gfaTransformedVertices = new Float32Array(4*iVertices); gfaVertexColors = new Float32Array(4*iVertices); CreateProgramAndContext(); // Begin the animation loop. const kiIntervalId = setInterval(Render, 20); } function Render() { // First copy the vertices before the transformation for (var i = 4*24; i < 4*24 + 16+4*giAddedPoints; ++i) { gfaTransformedVertices[i] = gfaVertices[i]; } // Color the first four vertices for (var i = 0; i < 4; ++i) { // Set the colors too gfaVertexColors[4*24 + 4*i] = 1.0; gfaVertexColors[4*24 + 4*i + 1] = 1.0; gfaVertexColors[4*24 + 4*i + 2] = 1.0; gfaVertexColors[4*24 + 4*i + 3] = 1.0; } for (var i = 0; i < giAddedPoints; ++i) { gfaVertexColors[4*28 + 4*i] = 0.0; gfaVertexColors[4*28 + 4*i + 1] = 0.0; gfaVertexColors[4*28 + 4*i + 2] = 1.0; gfaVertexColors[4*28 + 4*i + 3] = .75; } var faLookAtMatrix = CreateLookAtMatrix([1, 3, 2],[0, 0, 0],[0, 1, 0]); // Create the orthographic matrix var faOrthoMatrix = CreateOrthographicMatrix(-2.0, 2.0, -2.0, 2.0, 2.0, -2.0); MultiplyMatrices(faOrthoMatrix, faLookAtMatrix); // Add the extra colors for the view cube for (var iFace = 0; iFace < 6; ++iFace) { var fBrightness = 0.0; if (iFace % 3 == 0) { fBrightness = 1.0/7.0; } else if (iFace % 3 == 2) { fBrightness = 2.0/7.0; } else { fBrightness = 4.0/7.0; } var iBase = 16*iFace; for (var iVertex = 0; iVertex < 4; ++iVertex) { var iOffset = iBase + 4*iVertex; gfaTransformedVertices[iOffset] = gfaVertices[iOffset]; gfaTransformedVertices[iOffset + 1] = gfaVertices[iOffset + 1]; gfaTransformedVertices[iOffset + 2] = gfaVertices[iOffset + 2]; gfaTransformedVertices[iOffset + 3] = gfaVertices[iOffset + 3]; gfaVertexColors[iOffset] = fBrightness; gfaVertexColors[iOffset + 1] = fBrightness; gfaVertexColors[iOffset + 2] = fBrightness; gfaVertexColors[iOffset + 3] = .5; } } for (var i = 0; i < 28+4*giAddedPoints; ++i) { MultiplyMatrixVertex(faOrthoMatrix, gfaTransformedVertices, 4*i); } // We need to create the buffers afterward CreateBuffers(); gqGL.clearColor(0.0, 0.0, 0.0, 1.0); gqGL.enable(gqGL.DEPTH_TEST); gqGL.clear(gqGL.COLOR_BUFFER_BIT | gqGL.DEPTH_BUFFER_BIT); // Enable alpha blending gqGL.enable(gqGL.BLEND); // Set blending function gqGL.blendFunc(gqGL.SRC_ALPHA, gqGL.ONE_MINUS_SRC_ALPHA); gqGL.drawArrays(gqGL.POINTS, 24, 4); // This does not work well //const [kiMinSize, kiMaxSize] = gqGL.getParameter(gqGL.ALIASED_LINE_WIDTH_RANGE); //gqGL.lineWidth(kiMaxSize); //gqGL.drawArrays(gqGL.LINE_LOOP, 28, giAddedPoints); // The six sides gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 0, 4); gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 4, 4); gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 8, 4); gqGL.drawArrays(gqGL.TRIANGLE_FAN, 28, giAddedPoints); gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 12, 4); gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 16, 4); gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 20, 4); } // The plane is a 4d array of the form [A, B, C, D], where Ax + By + Cz + D = 0 // The box is a 6d array of the for [[xL, xH], [yL, yH], [zL, zH]], where xL and xH // are the low and high values of the x plane defining the box, and so on. function GetPlaneBoxIntersectionPolygon(faPlane, faaBox) { // The points of intersection var faPolyPoints = new Float32Array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); var iVertices = 0; // Intersect the plane with each side line of the form [xL, yL, 0] + t[0, 0, 1] = [x, y, z] // A(xL) + B(yL) + Ct + D = 0 => t = -(A*xL + B*yL + D)/C // Check whether the z-value = t is in the range [zL, zH]. If it is zL or zH, we have a triple intersection. // yz: 0-3; 0, zx: 4-7, xy: 8-11 : Individual edge indices are ordered ll, lh, hl, hh // An array of booleans used to indicate whether the intersection with the edge was already found. var baFound = [false, false, false, false, false, false, false, false, false, false, false, false]; // First the nonconstant dimension: the edges where x, y, and z vary, respectively for (var i = 0; i < 3; ++i) { if (faPlane[i] != 0.0) { // If the coefficeint is zero, it is parallel and does not intersect, but may coincide for (var j = 0; j < 2; ++j) { for (var k = 0; k < 2; ++k) { var iEdge = 4*i + 2*j + k; // This check is mostly used to avoid adding corner points three times. if (!baFound[iEdge]) { // Get the intersection // Check its range with the ith dimension // If it is in range log it // If it is a corner, log and remove the other two edges // Special case: The plane contains a line. In this case, we add both corners. (e.g. x + y = 0 and x = 1 and y = -1 are sides) // Specail case: The plane contains a side. In this case, we add all four vertices and quit. () // When x is nonconstant, we have x = -(By1 + Cz1 + D)/A, call the solution dSol var dSol = -faPlane[3]; // -D // So, loop to get the other terms for (var m = 1; m < 3; ++m) { var iLowHigh = ((m == 1) ? j : k); dSol -= faPlane[(i+m)%3]*faaBox[(i+m)%3][iLowHigh]; } // Finally divide by the coefficient, since it is not zero. dSol /= faPlane[i]; // Check whether the intersection is in the interval if (dSol >= faaBox[i][0] && dSol <= faaBox[i][1]) { // Take care of the special corner cases if (dSol == faaBox[(i+m)%3][0]) { // This dimension is low, So, it adds nothing to the index baFound[((i+1)%3)*4 + 2*k ] = true; baFound[((i+2)%3)*4 + j ] = true; } else if (dSol == faaBox[(k+m)%3][1]) { // This dimension is high, So, it adds to the index baFound[((i+1)%3)*4 + 2*k + 1 ] = true; baFound[((i+2)%3)*4 + 2 + j ] = true; } // Add the intersection and note it baFound[iEdge] = true; // Finally, add the point faPolyPoints[4*iVertices + i] = dSol; faPolyPoints[4*iVertices + ((i+1)%3)] = faaBox[((i+1)%3)][j]; faPolyPoints[4*iVertices + ((i+2)%3)] = faaBox[((i+2)%3)][k]; faPolyPoints[4*iVertices + 3] = 1.0; ++iVertices; } } } } } else { // In this case, the plane may align with an edge or multiple edges // Run though the four edges where this dimension is zero, and check whether the plane aligns with any of them for (var j = 0; j < 2; ++j) { for (var k = 0; k < 2; ++k) { var iEdge = 4*i + 2*j + k; if (!baFound[iEdge]) { var bIsOnPlane = (faPlane[(i+1)%3]*faaBox[(i+1)%3][j] + faPlane[(i+2)%3]*faaBox[(i+2)%3][k] + faPlane[3] == 0.0); if (bIsOnPlane) { faPolyPoints[4*iVertices + i] = faaBox[i][0]; faPolyPoints[4*iVertices + ((i+1)%3)] = faaBox[((i+1)%3)][j]; faPolyPoints[4*iVertices + ((i+2)%3)] = faaBox[((i+2)%3)][k]; faPolyPoints[4*iVertices + 3] = 1.0; ++iVertices; faPolyPoints[4*iVertices + i] = faaBox[i][1]; faPolyPoints[4*iVertices + ((i+1)%3)] = faaBox[((i+1)%3)][j]; faPolyPoints[4*iVertices + ((i+2)%3)] = faaBox[((i+2)%3)][k]; faPolyPoints[4*iVertices + 3] = 1.0; ++iVertices; baFound[iEdge] = true; // The low value. So, add nothing. baFound[((i+1)%3)*4 + 2*k] = true; baFound[((i+2)%3)*4 + j ] = true; // The high value. So, 1 or 2. baFound[((i+1)%3)*4 + 2*k + 1 ] = true; baFound[((i+2)%3)*4 + 2 + j ] = true; } } } } } } // Now we have all of the point of the polygon. We need to order them an put them into a returned polygon array. // If we only have one or two points, we could return them as a special case of a point and a line segment. For, we will return null for all. if (iVertices == 0) { return null; } else if (iVertices == 1){ return null; } else if (iVertices == 2){ return null; } // Start with the first point and find each successive point with a common side. // Find the one that is closest, in case the plane coincides with a side. for (var i = 0; i < iVertices - 2; ++i) { // Use the diagonal for the initial distance. var fMinDistSq = (faaBox[0][0]-faaBox[0][1])*(faaBox[0][0]-faaBox[0][1]) + (faaBox[1][0]-faaBox[1][1])*(faaBox[1][0]-faaBox[1][1]) + (faaBox[2][0]-faaBox[2][1])*(faaBox[2][0]-faaBox[2][1]); var iClosetOnSameSide = 0; for (var j = i+1; j < iVertices; ++j) { var bOnSameSide = false; for (var k = 0; k < 3; ++k) { if ((faPolyPoints[4*i + k] == faPolyPoints[4*j + k]) && (faPolyPoints[4*i + k] == faaBox[k][0] || faPolyPoints[4*i + k] == faaBox[k][1])) { bOnSameSide = true; } } if (bOnSameSide) { // Check whether the distance is smallest var fDistSq = 0.0 for (var k = 0; k < 3; ++k) { var dDiff = (faPolyPoints[4*i + k]-faPolyPoints[4*j + k]); fDistSq += dDiff*dDiff; } if (fDistSq < fMinDistSq) { iClosetOnSameSide = j; fMinDistSq = fDistSq; } } } // Now we have the index of the closet vertex that is on the same side, make it next for (var j = 0; j < 3; ++j) { // Swap the currrent coordinate for the first point var dSwap = faPolyPoints[4*(i + 1) + j]; faPolyPoints[4*(i + 1) + j] = faPolyPoints[4*iClosetOnSameSide + j]; faPolyPoints[4*iClosetOnSameSide + j] = dSwap; } } // Copy the points back into an array // I could add in a procedure to reverse the points if they are in the wrong order. They should probably be made to be always visible. var faPoly = new Float32Array(4*iVertices); for (var i = 0; i < 4*iVertices; ++i) { faPoly[i] = faPolyPoints[i]; } return faPoly; } // Multiply the four coordinate vertex in V at the start index function MultiplyMatrixVertex(faM, faV, iStart) { // V = M*V var faCopy = [0,0,0,0]; for (var i = 0; i < 4; ++i) { faCopy[i] = faV[iStart + i]; } for (iRow = 0; iRow < 4; ++iRow) { faV[iStart + iRow] = faM[iRow]*faCopy[0] + faM[iRow + 4]*faCopy[1] + faM[iRow + 8]*faCopy[2] + faM[iRow + 12]*faCopy[3]; } } function Normalize(faV) { var fL = Math.sqrt(faV[0]*faV[0] + faV[1]*faV[1] + faV[2]*faV[2]); faV[0] /= fL; faV[1] /= fL; faV[2] /= fL; } function Dot(faV1, faV2) { return (faV1[0]*faV2[0] + faV1[1]*faV2[1] + faV1[2]*faV2[2]); } function Cross(faV1, faV2) { return [faV1[1]*faV2[2]-faV1[2]*faV2[1], faV1[2]*faV2[0]-faV1[0]*faV2[2], faV1[0]*faV2[1]-faV1[1]*faV2[0]]; } function Difference(faV1, faV2) { return [faV1[0]-faV2[0], faV1[1]-faV2[1], faV1[2]-faV2[2]]; } function CreateLookAtMatrix(faEye, faObject, faUp) { var faViewDirection = Difference(faObject, faEye); Normalize(faViewDirection); var faRight = Cross(faViewDirection, faUp); Normalize(faRight); var faStraightUp = Cross(faRight, faViewDirection); var faMatrix = new Float32Array([ faRight[0], faStraightUp[0], faViewDirection[0], 0.0, faRight[1], faStraightUp[1], faViewDirection[1], 0.0, faRight[2], faStraightUp[2], faViewDirection[2], 0.0, -Dot(faObject, faRight), -Dot(faObject, faStraightUp), -Dot(faObject, faViewDirection), 1.0]); return faMatrix; } function CreateOrthographicMatrix(fLeft, fRight, fBottom, fTop, fNear, fFar) { if (fLeft >= fRight || fBottom >= fTop || fFar >= fNear) { throw 'Improper Orthographic Projection Matrix'; } fDx = fRight - fLeft; fDy = fTop - fBottom; fDz = fNear - fFar; var faMatrix = new Float32Array([ 2.0/fDx, 0.0, 0.0, 0.0, 0.0, 2.0/fDy, 0.0, 0.0, 0.0, 0.0, 2.0/fDz, 0.0, -(fLeft + fRight)/fDx, -(fBottom + fTop)/fDy, -(fNear + fFar)/fDz, 1.0]); return faMatrix; } function MultiplyMatrices(faaM, faaA) { // M = M*A, Note M != A var faRow = [0,0,0,0]; for (iRow = 0; iRow < 4; ++iRow) { // Copy the current row for(iCol = 0; iCol < 4; ++iCol) { faRow[iCol] = faaM[iRow + 4*iCol]; } for(iCol = 0; iCol < 4; ++iCol) { faaM[iRow + 4*iCol] = 0.0; for (k = 0; k < 4; ++k) { faaM[iRow + 4*iCol] += faRow[k]*faaA[4*iCol + k]; } } } } </script> </head> <body onload="Initialization()"> <canvas id="idCanvas" width="800", height="800" style="border:1px solid blue"></canvas> </body> </html>
© 20072025 XoaX.net LLC. All rights reserved.